
const Util = require('Util');

const RoadBlock = require('RoadBlock');
const Item = require('Item');

const RoadBlockFactory = require('RoadBlockFactory');


const CellType = {
    BLOCK: 1,
    ITEM: 2
};


cc.Class({
    extends: cc.Component,

    statics: {
        createNodeWithString(str) {
            let levelNode = new cc.Node();

            let level = levelNode.addComponent(this);
            level.initWithString(str);

            levelNode.destroy = function () {
                for (let [cellIdx, cellObj] of level.cellObjMap) {
                    cellObj instanceof cc.Component && cellObj.removeSelf();
                }

                this.constructor.prototype.destroy.call(this);

                // Util.delayFrameTime(3, () => {
                //     cc.log('===CELL-OBJs:', [...cellObjMap.values()].every(obj => cc.isValid(obj)));
                // });
            };

            return levelNode;
        }
    },

    properties: {
        columnNumber: 5
    },

    ctor() {
        this.cellSize = cc.size(216, 220);
        this.lineHeight = 0;

        this.cellStrs = [];

        this.cellStartPos = cc.p(0, 0);

        this.cellIdxRange = [0, 0];
        this.cellIdx = 0;

        this._rowIdxRangeInView = [];
        this._lastRowIdx = 0;

        this.cellObjMap = new Map();
        this.cellDataMap = new Map();
        this.rowHeightMap = new Map();
    },
    start() {
        // this.generate();
    },

    initWithString(str) {
        this.node.setAnchorPoint(0, 0);
        this.node.setContentSize(this.cellSize.width * this.columnNumber, 0);

        this.lineHeight = this.cellSize.height;

        this.cellStrs = str.split(',');
        if (this.cellStrs.length == 0) cc.error('level string invalid:', str);

        this.cellStartPos = cc.p(this.node.width, 0);

        this.cellIdxRange = [0, this.cellStrs.length - 1];
        this.cellIdx = this.cellIdxRange[1];  // end to start

        this._rowIdxRangeInView = [];
        this._lastRowIdx = Math.floor(this.cellIdx / this.columnNumber);

        this.cellObjMap = new Map();
        this.rowHeightMap = new Map();
    },
    generate(blank = false) {
        this.generateCells(blank);
    },

    generateView(viewHeight, viewOffset) {// partial
        let rowIdxRange = [0, 0],
            rangeIdx = 1;
        let offset = 0,
            startOffset = viewOffset;
        let endRowOffset = 0;

        for (let rowIdx = this._lastRowIdx; rowIdx >= 0; rowIdx--) {
            let rowHeight = this.rowHeightMap.get(rowIdx);
            if (!rowHeight) {
                this.generateRow(rowIdx, true);
                rowHeight = this.rowHeightMap.get(rowIdx);
            }

            offset += rowHeight;
            if (offset >= startOffset) {
                rowIdxRange[rangeIdx--] = rowIdx;

                if (rangeIdx == 0) {// end row
                    endRowOffset = offset - rowHeight;

                    startOffset += viewHeight;
                    if (offset >= startOffset) {
                        rowIdxRange[rangeIdx] = rowIdx;
                        break;
                    }
                } else {// start row
                    break;
                }
            }
        }

        // Expand row range
        rowIdxRange[0] = Math.max(0, rowIdxRange[0] - 1);
        rowIdxRange[1] = Math.min(this._lastRowIdx, rowIdxRange[1] + 1);
        if (endRowOffset > 0) {
            let rowHeight = this.rowHeightMap.get(rowIdxRange[1]);
            if (!rowHeight) {
                this.generateRow(rowIdxRange[1], true);
                rowHeight = this.rowHeightMap.get(rowIdxRange[1]);
            }
            endRowOffset -= rowHeight;
        }

        // cc.log('--L-G-V:', viewHeight, viewOffset, this._lastRowIdx, rowIdxRange, this._rowIdxRangeInView);
        if (rowIdxRange[0] == this._rowIdxRangeInView[0] && rowIdxRange[1] == this._rowIdxRangeInView[1])
            return this.isAllRowsDone();

        this.cellStartPos = cc.p(this.node.width, endRowOffset);

        this.cellIdxRange = [
            rowIdxRange[0] * this.columnNumber,
            Math.min(rowIdxRange[1] * this.columnNumber + this.columnNumber - 1, this.cellStrs.length - 1)
        ];
        this.cellIdx = this.cellIdxRange[1];
        // cc.log('==L-G-V:', this.cellStartPos, this.cellIdxRange, this.cellIdx);

        for (let [cellIdx, cellObj] of this.cellObjMap) {
            if (cellObj instanceof cc.Component && (this.cellIdxRange[0] > cellIdx || cellIdx > this.cellIdxRange[1])) {
                this.cellObjMap.delete(cellIdx);
                cellObj.removeSelf();
            }
        }

        if (rowIdxRange[0] < this._rowIdxRangeInView[0] || rowIdxRange[1] > this._rowIdxRangeInView) {
            this.generateCells();
            this.updateSize();
        }
        this._rowIdxRangeInView = rowIdxRange;

        return this.isAllRowsDone();
    },

    generateRow(rowIdx, blank = false) {
        this.cellIdxRange = [
            rowIdx * this.columnNumber,
            Math.max(rowIdx * this.columnNumber + this.columnNumber - 1, this.cellStrs.length - 1)
        ];
        this.cellIdx = this.cellIdxRange[1];

        this.lineHeight = this.cellSize.height;
        this.generateCells(blank);
    },

    generateCells(blank = false) {
        let rowIdx = Math.floor(this.cellIdx / this.columnNumber);
        let rowHeight = this.rowHeightMap.get(rowIdx);

        if (blank) {
            if (!rowHeight) {
                let cellStr = this.cellStrs[this.cellIdx];

                let cellInfo = this.createBlankCell(cellStr);
                if (cellInfo.data) {
                    this.cellDataMap.set(this.cellIdx, cellInfo.data);
                }

                this.lineHeight = Math.max(this.lineHeight, cellInfo.size.height);

                rowHeight = this.lineHeight;
                // cc.log('--L-G-Cs=b:', this.cellIdx, rowIdx, cellStr, cellInfo, rowHeight);
                if (this.cellIdx % this.columnNumber == 0) {
                    this.rowHeightMap.set(rowIdx, rowHeight);

                    this.lineHeight = 0;
                }
            }

            if (this.cellIdx != this.cellIdxRange[0]) {
                this.cellIdx--;
                this.generateCells(blank);
            }
            return;
        }


        let createCallback = (cellObj, isNew = true) => {
            if (cellObj instanceof cc.Component) {
                // cc.log('==L-C-C:', this.cellIdx, rowIdx, this.cellStrs[this.cellIdx], isNew && cellObj, rowHeight);
                if (!rowHeight) {
                    this.lineHeight = Math.max(this.lineHeight, cellObj.height);
                    // rowHeight = this.lineHeight;
                }

                if (isNew) {
                    cellObj.x = this.cellStartPos.x - this.cellSize.width / 2;
                    cellObj.y = this.cellStartPos.y + cellObj.height / 2;

                    let cellIdx = this.cellIdx;
                    this.cellObjMap.set(cellIdx, cellObj);

                    cellObj.node.once('removed', () => {
                        this.cellObjMap.set(cellIdx, 1);  // cell obj removed tag
                    });

                    this.node.emit('create-obj', cellObj);
                }
            }

            if (this.cellIdx % this.columnNumber == 0) {// line end
                this.cellStartPos.x = this.node.width;

                if (!rowHeight) {
                    rowHeight = this.lineHeight;
                    this.rowHeightMap.set(rowIdx, rowHeight);
                }
                this.cellStartPos.y += rowHeight;

                this.lineHeight = this.cellSize.height;
            } else {
                this.cellStartPos.x -= this.cellSize.width;
            }

            if (this.cellIdx != this.cellIdxRange[0]) {
                this.cellIdx--;
                this.generateCells(blank);
            } else {
                this.node.emit('complete');
            }
        };

        let cellObj = this.cellObjMap.get(this.cellIdx);
        if (cellObj) {
            createCallback(cellObj, false);
            return;
        }

        let cellStr = this.cellStrs[this.cellIdx];
        let cellData = this.cellDataMap.get(this.cellIdx);

        this.createCell(cellData || cellStr, createCallback);
    },

    createCell(cellStr, callback) {
        let cellData = {};
        if ( cc.js.isString(cellStr) ) {
            if ( cellStr.indexOf('-') > 0 ) {// block
                let [blockType, hpType] = cellStr.split('-');
                // cc.log('--BLOCK-CELL:',type,hpType);
                cellData.type = CellType.BLOCK;
                cellData.blockType = blockType;
                cellData.hpType = hpType;

            } else if (cellStr === '0') {// blank

            } else {// item
                let itemIds = cellStr.split('|');
                let itemType = itemIds[ Util.randomInt(0, itemIds.length - 1) ] - 4;  // item id starts from 5
                // cc.log('--ITEM-CELL:',itemIds,itemType);
                cellData.type = CellType.ITEM;
                cellData.itemType = itemType;
            }
        } else {// cellData
            cellData = cellStr;
        }

        switch (cellData.type) {
            case CellType.BLOCK:
                RoadBlockFactory.createBlock(cellData.blockType, cellData.hpType, (block) => {
                    if (block.type == RoadBlock.Type.BIG) {
                        block.move();
                    }
                    block.setBoundary([0, this.node.width]);

                    block.node.parent = this.node;

                    block.on('hit-bullet', (event) => {
                        this.node.emit('hit-bullet', {block, bullet: event.detail});
                    });

                    callback(block);
                });
                break;
            case CellType.ITEM:
                if (cellData.itemType <= 0) {
                    callback();
                    break;
                }

                RoadBlockFactory.createItem(cellData.itemType, (item) => {
                    item.node.parent = this.node;

                    callback(item);
                });
                break;
            default:
                callback();
        }
    },
    createBlankCell(cellStr) {
        if ( cellStr.indexOf('-') > 0 ) {// block
            let [type, hpType] = cellStr.split('-');

            return {size: RoadBlockFactory.getBlockSize(type)};
        } else if (cellStr === '0') {// blank
            return {size: cc.size(this.cellSize)};
        } else {// item
            let itemIds = cellStr.split('|');
            let itemType = itemIds[ Util.randomInt(0, itemIds.length - 1) ] - 4;

            return {
                size: itemType < 0 ? cc.size(this.cellSize) : RoadBlockFactory.getItemSize(itemType),
                data: {type: CellType.ITEM, itemType}
            };
        }
    },

    updateSize() {
        let height = 0;
        let rowIdx = this._lastRowIdx;
        for (let rowHeight; rowHeight = this.rowHeightMap.get(rowIdx--);) {
            height += rowHeight;
        }
        // cc.log('--L-U-S:', height, this.rowHeightMap);
        this.node.height = height;

        let allRowsDone = rowIdx < 0;
        this.node.emit('update-size', allRowsDone);

        return allRowsDone;
    },
    isAllRowsDone() {
        return this.rowHeightMap.size == this._lastRowIdx + 1;
    },

    getRowIndexAt(viewOffset) {
        let offset = 0;
        for (let rowIdx = this._lastRowIdx; rowIdx >= 0; rowIdx--) {
            offset += this.rowHeightMap.get(rowIdx);
            if (offset >= viewOffset)
                return rowIdx;
        }
    },

    clearView(cellType) {
        let classFn = cc.Component,
            classMethod = 'removeSelf';

        if (cellType == CellType.BLOCK) {
            classFn = RoadBlock;
            classMethod = 'die';
        } else if (cellType == CellType.ITEM) {
            classFn = Item;
        }

        for (let cellIdx = this.cellIdxRange[0], endCellIdx = this.cellIdxRange[1]; cellIdx <= endCellIdx; cellIdx++) {
            let cellObj = this.cellObjMap.get(cellIdx);
            if (cellObj instanceof classFn) {
                cellObj[classMethod]();
            }
        }
    },
    clearBlocks() {
        this.clearView(CellType.BLOCK);
    }
});